package com.yanggu.payment.service.impl;

import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.wechat.pay.contrib.apache.httpclient.notification.Notification;
import com.wechat.pay.contrib.apache.httpclient.notification.NotificationHandler;
import com.wechat.pay.contrib.apache.httpclient.notification.NotificationRequest;
import com.yanggu.payment.config.WeiXinPayConfig;
import com.yanggu.payment.entity.OrderInfo;
import com.yanggu.payment.entity.PaymentInfo;
import com.yanggu.payment.entity.RefundInfo;
import com.yanggu.payment.entity.weixinpay.req.PrepayAmount;
import com.yanggu.payment.entity.weixinpay.req.WechatAppPayCloseParams;
import com.yanggu.payment.entity.weixinpay.req.WechatAppPrepayParams;
import com.yanggu.payment.entity.weixinpay.req.WeiXinPayApplyRefundReq;
import com.yanggu.payment.entity.weixinpay.resp.RefundAmount;
import com.yanggu.payment.entity.weixinpay.resp.WechatAppQueryResult;
import com.yanggu.payment.entity.weixinpay.resp.WeiXinPayApplyRefundResp;
import com.yanggu.payment.entity.weixinpay.resp.WeiXinPayRefundNotify;
import com.yanggu.payment.enums.OrderStatus;
import com.yanggu.payment.enums.PayType;
import com.yanggu.payment.enums.wxpay.WxNotifyType;
import com.yanggu.payment.enums.wxpay.WxRefundStatus;
import com.yanggu.payment.feign.weixin_pay.WeiXinPayFeign;
import com.yanggu.payment.service.OrderInfoService;
import com.yanggu.payment.service.PaymentService;
import com.yanggu.payment.service.RefundInfoService;
import com.yanggu.payment.service.WeiXinPayService;
import com.yanggu.payment.vo.R;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * @version V1.0
 * @author: YangGu
 * @date: 2022/5/4 23:36
 * @description:
 */
@Slf4j
@Service
public class WeiXinPayServiceImpl implements WeiXinPayService {

    @Autowired
    private OrderInfoService orderInfoService;

    @Autowired
    private PaymentService paymentService;

    @Autowired
    private RefundInfoService refundInfoService;

    @Autowired
    private WeiXinPayConfig weiXinPayConfig;

    @Autowired
    private WeiXinPayFeign weiXinPayFeign;

    @Autowired
    private NotificationHandler notificationHandler;

    /**
     * 支付回调锁
     * 推荐使用分布式锁(Redisson)
     */
    private final Lock payNotifyLock = new ReentrantLock();

    /**
     * 退款回调锁
     */
    private final Lock refundNotifyLock = new ReentrantLock();

    @Transactional(rollbackFor = RuntimeException.class)
    @Override
    public Map<String, Object> nativePay(Long productId) {
        //获取或者生成订单
        OrderInfo orderInfo = orderInfoService.createOrderByProductId(productId, PayType.WXPAY.getType());
        String codeUrl = orderInfo.getCodeUrl();
        String orderNo = orderInfo.getOrderNo();

        Map<String, Object> map = new HashMap<>(2);
        //如果订单已经存在直接返回即可
        if (StrUtil.isNotBlank(codeUrl)) {
            log.info("订单已存在，二维码已保存");
            //返回二维码
            map.put("codeUrl", codeUrl);
            map.put("orderNo", orderNo);
            return map;
        }
        WechatAppPrepayParams nativeOrderApiReq = new WechatAppPrepayParams();
        //设置商户id
        nativeOrderApiReq.setMchId(weiXinPayConfig.getMchId());
        //设置订单编号
        nativeOrderApiReq.setOutTradeNo(orderNo);
        //设置appid
        nativeOrderApiReq.setAppId(weiXinPayConfig.getAppid());
        //设置描述信息
        nativeOrderApiReq.setDescription(orderInfo.getTitle());
        //设置回调地址
        nativeOrderApiReq.setNotifyUrl(weiXinPayConfig.getNotifyDomain().concat(WxNotifyType.NATIVE_NOTIFY.getType()));
        //设置金额
        PrepayAmount amount = new PrepayAmount();
        nativeOrderApiReq.setAmount(amount);
        amount.setTotal(orderInfo.getTotalFee());
        amount.setCurrency("CNY");
        log.info("微信支付native下单API请求参数: {}", JSON.toJSONString(nativeOrderApiReq));
        // 请求body参数
        /*
        {
            "mchid": "1900006XXX",
            "out_trade_no": "native12177525012014070332333",
            "appid": "wxdace645e0bc2cXXX",
            "description": "Image形象店-深圳腾大-QQ公仔",
            "notify_url": "https://weixin.qq.com/",
            "amount": {
                "total": 1,
                "currency": "CNY"
            }
        }
        */
        //调用统一下单API
        JSONObject jsonObject = weiXinPayFeign.nativeOrderApi(nativeOrderApiReq);
        //响应示例
        /*
        {
            "code_url": "weixin://wxpay/bizpayurl?pr=p4lpSuKzz"
        }
        */
        codeUrl = jsonObject.getString("code_url");
        map.put("codeUrl", codeUrl);
        map.put("orderNo", orderNo);
        orderInfo.setCodeUrl(codeUrl);
        orderInfoService.updateById(orderInfo);

        return map;
    }

    @Override
    public WechatAppQueryResult queryOrder(String orderNo) {
        return weiXinPayFeign.queryForOutTradeNo(orderNo, weiXinPayConfig.getMchId());
    }

    @Override
    @Transactional(rollbackFor = RuntimeException.class)
    public R nativeNotify(String body, String serialNumber, String nonce, String timestamp, String signature) throws Exception {
        //处理签名和解密数据
        WechatAppQueryResult wechatAppQueryResult = parse(body, serialNumber, nonce, timestamp, signature, WechatAppQueryResult.class);
        //处理回调函数的数据
        handlerWeiXinPaySuccess(wechatAppQueryResult);

        return R.ok();
    }

    @Override
    @Transactional(rollbackFor = RuntimeException.class)
    public R refundsNotify(String body, String serialNumber, String nonce, String timestamp, String signature) throws Exception {
        //处理签名和解密数据
        WeiXinPayRefundNotify refundNotify = parse(body, serialNumber, nonce, timestamp, signature, WeiXinPayRefundNotify.class);

        //处理退款回调数据
        handlerWeiXinRefundNotify(refundNotify);

        return R.ok();
    }

    @Override
    @Transactional(rollbackFor = RuntimeException.class)
    public void handlerWeiXinRefundNotify(WeiXinPayRefundNotify refundNotify) {
        refundNotifyLock.lock();
        try {
            //更改退款记录表状态
            RefundInfo refundInfo = new RefundInfo();
            String refundStatus = refundNotify.getRefundStatus();
            refundInfo.setRefundStatus(refundStatus);
            refundInfo.setContentNotify(JSON.toJSONString(refundNotify));

            LambdaQueryWrapper<RefundInfo> refundWrapper = Wrappers.<RefundInfo>lambdaQuery()
                    .eq(RefundInfo::getRefundNo, refundNotify.getOutRefundNo());

            refundInfoService.update(refundInfo, refundWrapper);

            //更改订单状态
            OrderInfo orderInfo = new OrderInfo();
            String orderStatus;
            if (StrUtil.equals(refundStatus, WxRefundStatus.SUCCESS.getType())) {
                orderStatus = OrderStatus.REFUND_SUCCESS.getType();
            } else if (StrUtil.equals(refundStatus, WxRefundStatus.ABNORMAL.getType())) {
                orderStatus = OrderStatus.REFUND_ABNORMAL.getType();
            } else if (StrUtil.equals(refundStatus, WxRefundStatus.CLOSED.getType())) {
                orderStatus = OrderStatus.REFUND_CLOSE.getType();
            } else {
                throw new RuntimeException("未知的微信退款状态" + refundStatus);
            }
            orderInfo.setOrderStatus(orderStatus);

            LambdaQueryWrapper<OrderInfo> orderWrapper = Wrappers.<OrderInfo>lambdaQuery()
                    .eq(OrderInfo::getOrderNo, refundNotify.getOutTradeNo());
            orderInfoService.update(orderInfo, orderWrapper);
        } finally {
            refundNotifyLock.unlock();
        }
    }

    @Override
    @Transactional(rollbackFor = RuntimeException.class)
    public void handlerWeiXinPaySuccess(WechatAppQueryResult wechatAppQueryResult) {

        payNotifyLock.lock();
        try {
            //先查询订单状态, 如果是未支付再进行修改
            String outTradeNo = wechatAppQueryResult.getOutTradeNo();
            LambdaQueryWrapper<OrderInfo> wrapper = Wrappers.<OrderInfo>lambdaQuery()
                    .eq(OrderInfo::getOrderNo, outTradeNo);
            OrderInfo dbOrderInfo = orderInfoService.getOne(wrapper);
            if (dbOrderInfo == null) {
                return;
            }

            //当业务系统响应超时时, 微信支付服务器也会进行多次调用
            //TimeUnit.SECONDS.sleep(10L);

            if (!StrUtil.equals(OrderStatus.NOTPAY.getType(), dbOrderInfo.getOrderStatus())) {
                log.info("已经处理过微信支付通知: 订单号: {}", outTradeNo);
                return;
            }

            //修改订单状态
            OrderInfo orderInfo = new OrderInfo();
            orderInfo.setOrderStatus(OrderStatus.SUCCESS.getType());
            //添加幂等性判断。更新时间是否相等, 状态是否变更
            LambdaQueryWrapper<OrderInfo> queryWrapper = Wrappers.<OrderInfo>lambdaQuery()
                    .eq(OrderInfo::getOrderNo, outTradeNo)
                    .eq(OrderInfo::getOrderStatus, OrderStatus.NOTPAY.getType())
                    .eq(OrderInfo::getUpdateTime, dbOrderInfo.getUpdateTime());
            boolean result = orderInfoService.update(orderInfo, queryWrapper);
            if (!result) {
                log.info("支付回调订单状态更新失败: 订单号: {}", outTradeNo);
                return;
            }
            log.info("订单状态更新为支付成功, 订单号: {}", outTradeNo);

            //生成支付记录表
            PaymentInfo paymentInfo = new PaymentInfo();
            paymentInfo.setOrderNo(outTradeNo);
            paymentInfo.setTransactionId(wechatAppQueryResult.getTransactionId());
            paymentInfo.setPaymentType(PayType.WXPAY.getType());
            paymentInfo.setTradeType(wechatAppQueryResult.getTradeType());
            paymentInfo.setTradeState(wechatAppQueryResult.getTradeState());
            paymentInfo.setPayerTotal(wechatAppQueryResult.getAmount().getPayerTotal());

            paymentService.save(paymentInfo);
        } finally {
            payNotifyLock.unlock();
        }
    }

    @Override
    public String downloadBill(String billDate, String type) {
        JSONObject jsonObject = weiXinPayFeign.tradebill(type, billDate);
        //https://api.mch.weixin.qq.com/v3/billdownload/file?token=6XIv5TUPto7pByrTQKhd6kwvyKLG2uY2wMMR8cNXqaA_Cv_isgaUtBzp4QtiozLO
        String downloadUrl = jsonObject.getString("download_url");
        String token = downloadUrl.substring(downloadUrl.lastIndexOf("=") + 1);
        return weiXinPayFeign.downloadBill(token);
    }

    @Override
    @Transactional(rollbackFor = RuntimeException.class)
    public void cancelOrder(String orderNo) {
        //修改订单状态为用户取消
        OrderInfo orderInfo = new OrderInfo();
        orderInfo.setOrderStatus(OrderStatus.CANCEL.getType());
        LambdaQueryWrapper<OrderInfo> queryWrapper = Wrappers.<OrderInfo>lambdaQuery()
                .eq(OrderInfo::getOrderNo, orderNo)
                ;
        orderInfoService.update(orderInfo, queryWrapper);

        //调用微信支付 取消订单API
        WechatAppPayCloseParams params = new WechatAppPayCloseParams();
        params.setMchId(weiXinPayConfig.getMchId());
        weiXinPayFeign.cancelOrder(orderNo, params);
    }

    @Override
    @Transactional(rollbackFor = RuntimeException.class)
    public void refunds(String orderNo, String reason) {
        //生成退款单
        RefundInfo refundInfo = refundInfoService.createRefundInfo(orderNo, reason);

        WeiXinPayApplyRefundReq req = new WeiXinPayApplyRefundReq();
        req.setOutTradeNo(orderNo);
        req.setReason(reason);
        req.setOutRefundNo(refundInfo.getRefundNo());
        req.setNotifyUrl(weiXinPayConfig.getNotifyDomain().concat(WxNotifyType.REFUND_NOTIFY.getType()));
        RefundAmount refundAmount = new RefundAmount();
        refundAmount.setRefund(refundInfo.getRefund());
        refundAmount.setTotal(refundInfo.getTotalFee());
        refundAmount.setCurrency("CNY");
        req.setAmount(refundAmount);

        log.info("微信支付 申请退款请求参数: {}", JSON.toJSONString(req));
        //调用微信申请退单API
        WeiXinPayApplyRefundResp weiXinPayApplyRefundResp = weiXinPayFeign.applyRefund(req);
        log.info("微信支付 申请退款响应参数: {}", JSON.toJSONString(weiXinPayApplyRefundResp));

        //更新本地退单记录数据
        refundInfo.setRefundId(weiXinPayApplyRefundResp.getRefundId());
        refundInfo.setRefundStatus(weiXinPayApplyRefundResp.getStatus());
        refundInfo.setContentReturn(JSON.toJSONString(weiXinPayApplyRefundResp));
        refundInfoService.updateById(refundInfo);

        //更新本地订单状态
        OrderInfo orderInfo = new OrderInfo();
        orderInfo.setOrderStatus(OrderStatus.REFUND_PROCESSING.getType());
        LambdaQueryWrapper<OrderInfo> wrapper = Wrappers.<OrderInfo>lambdaQuery().eq(OrderInfo::getOrderNo, orderNo);
        orderInfoService.update(orderInfo, wrapper);

    }

    @Override
    public WeiXinPayApplyRefundResp queryRefundNo(String refundNo) {
        return weiXinPayFeign.queryRefundNo(refundNo);
    }


    private  <T> T parse(String body, String serialNumber, String nonce, String timestamp, String signature, Class<T> clazz) throws Exception{
        // 构建request，传入必要参数
        NotificationRequest request = new NotificationRequest.Builder().withSerialNumber(serialNumber)
                .withNonce(nonce)
                .withTimestamp(timestamp)
                .withSignature(signature)
                .withBody(body)
                .build();
        log.info("微信回调, request参数: {}", JSON.toJSONString(request));
        //验证通知签名
        Notification notification = notificationHandler.parse(request);
        //获取解密数据
        String decryptData = notification.getDecryptData();
        log.info("微信回调响应参数: {}", decryptData);
        return JSON.parseObject(decryptData, clazz);
    }

}
